/* hosts/amiga/signal.c
 **
 ** Adapt the default-signal handling for inclusion of timer-
 ** and user-generated signals. In detail this means setting the
 ** task_exception_code hook on a stub which 'raise()s' the appropriate
 ** signals.
 ** This is closely coupled with the timer functions so they are also
 ** implemented here.
 **
 ** LPMud uses the signals SIGHUP (driver process abortion by the user),
 ** SIGALRM (alarm timed out) and an interrupt signal for select().
 ** Amylaar additionally uses SIGUSR1 (update of the master by the user).
 ** These three signals are not caused by other program parts (as the standard
 ** implementation assumes), but instead are caused by timer time-outs or
 ** keypresses. Therefore these are implemented explicitely by using
 ** the Amiga's internal task signal system, which allows the setting of
 ** an exception handler which is called whenever a task signal is raised.
 ** The other signals are passed through to the standard clib signal().
 **
 ** For easier compilation, the catch_exception() which needs registerized
 ** args is put in a separate file signal_rr.c .
 **
 ** This code is based on the UnixLib by Erik van Roode.
 **
 **   18-Oct-92 [lars]  Done for DICE 2.06.40
 **   24-Feb-93 [lars]  Small fix to support compilation for OS 1.3
 **   28-Feb-93 [lars]  Moved to DICE 2.07.53
 **   09-Feb-93 [lars]  Added check_signals() and default break handling.
 */

#include <sys/types.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/tasks.h>
#include <exec/interrupts.h>
#include <devices/timer.h>
#ifdef INCLUDE_VERSION
#include <dos/dos.h>
#include <clib/alib_protos.h>
#include <clib/dos_protos.h>
#include <clib/exec_protos.h>
#else
#include <libraries/dos.h>
#endif
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include "nsignal.h"
#undef signal			/* This macro is implemented here! */

/*-----------------------------------------------------------------------*/

extern __regargs __geta4 ULONG catch_exception(__D0 ULONG);

ULONG sys_signal_alarm = 0, /* The system-signal-masks */
sys_signal_hup = 0, sys_signal_usr = 0;

void (*handler_hup)(void), (*handler_alarm)(void), (*handler_usr)(void);

static struct timerequest *treq = NULL; /* the alarm()-timer */

/*-----------------------------------------------------------------------
 ** int start_timer (struct timeval *tv, struct timerequest *tr)
 **
 **   Start a timer <tr> with given timeval <tv>.
 */

__stkargs int start_timer(struct timeval * tv, struct timerequest * tr)
{
	if (!tr)
	{
		printf("no request structure\n");
		return 0;
	}
	if (tv->tv_secs == 0L && tv->tv_micro < 2L)
		tv->tv_micro = 2L; /* minimal delay */

	tr->tr_time = *tv;
	tr->tr_node.io_Command = TR_ADDREQUEST;

	SendIO((struct IORequest *) tr);
	return 1;
}

/*-----------------------------------------------------------------------
 ** int setup_timer (LONG unit, struct timerequest **tr)
 **
 **   Setup a timer counting in <unit>, and store it in <tr>.
 */

__stkargs int setup_timer(LONG unit, struct timerequest ** tr)
{
	struct MsgPort *timerport;
	struct timerequest *req;

	if (*tr)
		return 1;

	if (!(timerport = (struct MsgPort *) CreatePort(0L, 0L)))
	{
		*tr = NULL;
		printf("setup_timer: could not create port\n");
		return 0;
	}
	if (!(req = (struct timerequest *) CreateExtIO(timerport,
	        sizeof(struct timerequest)) ))
	{
		DeletePort(timerport);
		*tr = NULL;
		printf("setup_timer: could not get request\n");
		return 0;
	}
	if (OpenDevice(TIMERNAME, unit, (struct IORequest *) req, 0L))
	{
		CloseDevice((struct IORequest *) req);
		DeleteExtIO((struct IORequest *) req);
		DeletePort(timerport);
		printf("setup_timer: could not open timer\n");
		*tr = NULL;
		return 0;
	}
	*tr = req;
	return 1;
}

/*-----------------------------------------------------------------------
 ** void cleanup_timer (struct timerequest **tr)
 **
 **   Cleanup given timer <tr>.
 */

__stkargs void cleanup_timer(struct timerequest ** tr)
{
	struct MsgPort *tp;
	struct timerequest *tmp;
	UBYTE pFlags;

	if (*tr)
	{
		tmp = *tr;
		tp = tmp->tr_node.io_Message.mn_ReplyPort;
		if (tp)
		{
			/* abort the current request */
			pFlags = tp->mp_Flags; /* still needed for DeletePort */
			tp->mp_Flags = PA_IGNORE;
			AbortIO((struct IORequest *) tmp);
			WaitIO((struct IORequest *) tmp);
			while (GetMsg(tp))
				;
			Forbid();
			tp->mp_Flags = pFlags;
			DeletePort(tp);
			Permit();
		}
		CloseDevice((struct IORequest *) tmp);
		DeleteExtIO((struct IORequest *) tmp);
	}
	*tr = NULL;
}

/*-----------------------------------------------------------------------
 ** __stkargs int alarm (int seconds)
 **
 **   Start a timer which raises SIGALRM after <seconds>.
 **   Also cleans up the mess made by a previous alarm.
 **   Specifying a zero time count just cleans up.
 */

__stkargs unsigned int alarm(unsigned int seconds)
{
	static struct timeval tv;
	static first = 1;

	if (!treq)
	{
		printf("No handler installed !\n");
		if (seconds > 0)
			return 0; /* Heartbeat won't work :+( */
	}
	tv.tv_secs = seconds;
	tv.tv_micro = 0;

	if (seconds > 0)
	{
		/* first call of alarm() : WaitIO on unsent request ..... */
		if (!first)
		{
			treq->tr_node.io_Message.mn_ReplyPort->mp_Flags = PA_IGNORE;
			AbortIO((struct IORequest *) treq);
			WaitIO((struct IORequest *) treq);
			treq->tr_node.io_Message.mn_ReplyPort->mp_Flags = PA_SIGNAL;
		}
		first = 0;
		start_timer(&tv, treq);
	}
	else
	{
		/*
		 * if I don't use this code, AbortIO will generate a signal, which
		 * will trigger catch_alarm. catch_alarm will then generate CTRL-E.
		 * This can be resolved by preventing the signal to occur :+)
		 */
		treq->tr_node.io_Message.mn_ReplyPort->mp_Flags = PA_IGNORE;
		AbortIO((struct IORequest *) treq);
		WaitIO((struct IORequest *) treq);
		cleanup_timer(&treq);
		first = 1;
	}
	return 0;
}

/*-----------------------------------------------------------------------
 ** __sigfunc new_signal (int signo, __sigfunc handler)
 **
 **   Set the <handler> for <signo>.
 **   The signals SIGHUP, SIGUSR1 and SIGALRM are treated manually to
 **   allow the system-signals call the handlers via external exceptions.
 */

__stkargs __sigfunc new_signal(int signo, __sigfunc handler)
{
	register struct Task *this_task;

	this_task = (struct Task *) FindTask(NULL);

	switch (signo)
	{
		case SIGALRM:
		{
			ULONG sigalrm;

			sigalrm = sys_signal_alarm;
			if ((__sigfunc) handler == SIG_IGN)
			{ /* remove SIGALRM
			 * handler */
				SetExcept(0L, sigalrm); /* Only sigalrm !! */
				sys_signal_alarm = 0;
				handler_alarm = NULL;
				cleanup_timer(&treq);
			}
			else
			{ /* install handler */
				if (!setup_timer(UNIT_VBLANK, &treq))
				{
					printf("Could not setup_timer\n");
					break; /* What else ?? */
				}
				sigalrm = 1L << (treq->tr_node.io_Message.mn_ReplyPort->mp_SigBit);

				this_task->tc_ExceptCode = (APTR) catch_exception;
				sys_signal_alarm = sigalrm;
				handler_alarm = (void (*) ()) handler;
				SetExcept(sigalrm, sigalrm);
				/* If we start treq, handler will be called */
			}
			break;
		}
		case SIGHUP:
		{
			ULONG sighup;

			sighup = (((__sigfunc) handler == SIG_IGN) || ((__sigfunc) handler == SIG_DFL) )
			? 0 : EXT_SIGHUP;

			this_task->tc_ExceptCode = (APTR) catch_exception;
			sys_signal_hup = sighup;
			handler_hup = (void(*)()) handler;
			SetExcept(sighup, EXT_SIGHUP);
			break;
		}
		case SIGUSR1:
		{
			ULONG sigusr;

			sigusr = (((__sigfunc) handler == SIG_IGN) || ((__sigfunc) handler == SIG_DFL))
			? 0 : EXT_SIGUSR;

			this_task->tc_ExceptCode = (APTR) catch_exception;
			sys_signal_usr = sigusr;
			handler_usr = (void(*)()) handler;
			SetExcept(sigusr, EXT_SIGUSR);
			break;
		}
		default:
			signal(signo, handler);
			break;
	}
	return handler;
}

/*-----------------------------------------------------------------------
 ** ULONG check_signals (void)
 **
 **   Check the tasks external signals and call the associated handler
 **   (if any).
 **   Result is the signal mask.
 */

static int _ChkSignalLockout = 0; /* simple semaphore for
 * check_signals() */

ULONG check_signals(void)
{
	ULONG mask;

	if (_ChkSignalLockout)
		return 0L;
	++_ChkSignalLockout;

	mask = ((struct Task *) FindTask(NULL))->tc_SigRecvd;

	/* Default Ctrl-C handling */
	if (!sys_signal_hup && (mask & SIGBREAKF_CTRL_C))
	{
		SetSignal(0L, SIGBREAKF_CTRL_C);
		write(2, "*** Break.\n", 11);
		exit(EXIT_FAILURE);
	}
	/* Handle our special exceptions */
	if (mask & sys_signal_alarm)
	{
		(*handler_alarm)();
		SetSignal(0L, sys_signal_alarm);
	}
	if (mask & sys_signal_hup)
	{
		(*handler_hup)();
		SetSignal(0L, sys_signal_hup);
	}
	if (mask & sys_signal_usr)
	{
		(*handler_usr)();
		SetSignal(0L, sys_signal_usr);
	}
	--_ChkSignalLockout;
	return mask;
}

/*************************************************************************/
